/**
 * @(#)Watcher.java		
 * 
 * History:
 * 1.0	30 April 2012
 * 		Added behaviour to subscribe to the Referee service
 * 1.1  2 May 2012
 * 		Moved from upm.pb4agent.common to upm.pb4agent.team1.platform
 * 		Extend AgBase1 instead of Agent
 * 1.2 	7 May 2012
 * 		Add protocol to messages that involve the use of the ontology
 * 2.0	12 May 2012
 * 		Added behaviour to create boat from RegistrationDesk
 * 3.0	18 May 2012
 * 		Changed ReceiveStartRaceSignal into ReceiveRaceStartedSignal
 * 		Added action: assign boat to manager
 * 		Added behaviour to receive row request from rower
 * 3.1	21 May 2012
 * 		Applied single Watcher per team.
 * 		Removed ReceiveCreateBoatMessage
 */
package upm.pb4agent.team1.platform;

import jade.content.Concept;
import jade.content.ContentElement;
import jade.content.lang.Codec.CodecException;
import jade.content.onto.OntologyException;
import jade.content.onto.UngroundedException;
import jade.content.onto.basic.Action;
import jade.core.AID;
import jade.core.Agent;
import jade.core.behaviours.SimpleBehaviour;
import jade.domain.FIPAException;
import jade.lang.acl.ACLMessage;
import jade.lang.acl.MessageTemplate;
import jade.wrapper.StaleProxyException;

import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;

import upm.pb4agent.ontology.AssignBoat;
import upm.pb4agent.ontology.DenyRow;
import upm.pb4agent.ontology.RaceFinished;
import upm.pb4agent.ontology.RaceStarted;
import upm.pb4agent.ontology.RealRow;
import upm.pb4agent.ontology.Row;
import upm.pb4agent.ontology.Seat;
import upm.pb4agent.ontology.SubscribeReferee;
import upm.pb4agent.team1.AgBase1;
import upm.pb4agent.team1.ontology.InformCoordinate;
import upm.pb4agent.team1.team.Constant;
import upm.pb4agent.team1.util.AgentUtil;
import upm.pb4agent.team1.util.LocalRower;

/**
 * This class handles the Watcher functions: (1) Subscribe to the service
 * provided by the Referee. (2) Translate the power of rowers into numerical
 * value.
 * 
 * @version 3.1
 * @author Bob Nicolaus
 * 
 */
public class Watcher extends AgBase1 {

	private static final long serialVersionUID = -402906878849343945L;

	private boolean isRaceStarted = false;
	private boolean isRaceFinished = false;

	private List<AID> rowerList;
	private String teamName;
	private AID boatAID;

	private HashMap<AID, BoatCoordinate> boatHashMap = new HashMap<AID, BoatCoordinate>();
	private HashMap<AID, LocalRower> rowerHashMap = new HashMap<AID, LocalRower>();

	private float rowSeconds;

	private float rower1Strength;
	private float rower2Strength;
	private float rower3Strength;
	private float rower4Strength;
	private float rower1Resistance;
	private float rower2Resistance;
	private float rower3Resistance;
	private float rower4Resistance;
	private float rower1Agility;
	private float rower2Agility;
	private float rower3Agility;
	private float rower4Agility;

	@Override
	protected void setup() {
		System.out.println(getLocalName() + ": Enters the system");

		Object[] args = getArguments();
		if (args != null) {
			rowerList = (List<AID>) args[0];
			teamName = (String) args[1];
		}

		try {

			registerAgent(Constant.WATCHER_DF_TYPE + teamName);

			rowSeconds = 1000 * Float.valueOf(AgentUtil
					.getProperty("row_seconds"));

			rower1Strength = Float.valueOf(AgentUtil
					.getProperty("rower1_strength"));
			rower1Resistance = Float.valueOf(AgentUtil
					.getProperty("rower1_resistance"));
			rower1Agility = Float.valueOf(AgentUtil
					.getProperty("rower1_agility"));
			rower2Strength = Float.valueOf(AgentUtil
					.getProperty("rower2_strength"));
			rower2Resistance = Float.valueOf(AgentUtil
					.getProperty("rower2_resistance"));
			rower2Agility = Float.valueOf(AgentUtil
					.getProperty("rower2_agility"));
			rower3Strength = Float.valueOf(AgentUtil
					.getProperty("rower3_strength"));
			rower3Resistance = Float.valueOf(AgentUtil
					.getProperty("rower3_resistance"));
			rower3Agility = Float.valueOf(AgentUtil
					.getProperty("rower3_agility"));
			rower4Strength = Float.valueOf(AgentUtil
					.getProperty("rower4_strength"));
			rower4Resistance = Float.valueOf(AgentUtil
					.getProperty("rower4_resistance"));
			rower4Agility = Float.valueOf(AgentUtil
					.getProperty("rower4_agility"));
		} catch (NumberFormatException e) {
			e.printStackTrace();
		} catch (FIPAException e) {
			e.printStackTrace();
		}

		// Create boat
		createBoat();

		// Subscribe to Referee
		try {
			subscribeRefereeService();
		} catch (CodecException ce) {
			ce.printStackTrace();
		} catch (OntologyException oe) {
			oe.printStackTrace();
		} catch (FIPAException e) {
			e.printStackTrace();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}

		// Add behaviours
		addBehaviour(new ReceiveRaceStartedSignal(this));
		addBehaviour(new ReceiveRaceFinishedSignal(this));
		addBehaviour(new ReceiveRowRequest(this));
	}

	/**
	 * Creates boat
	 */
	private void createBoat() {

		try {
			String boatName = "Boat" + teamName;
			createNewAgent(boatName, Boat.class.getCanonicalName(),
					new Object[] { teamName });
			boatAID = new AID(boatName, AID.ISLOCALNAME);

			// Insert into boat table
			boatHashMap.put(boatAID, new BoatCoordinate());

			// Assign boat to the rowers of the team
			Iterator<AID> rowerIterator = rowerList.iterator();

			upm.pb4agent.ontology.Boat boat = initiateBoat(boatAID);
			AssignBoat assignBoat = new AssignBoat();
			assignBoat.setBoat(boat);
			AID rowerAID = null;
			Action action = null;
			while (rowerIterator.hasNext()) {

				rowerAID = rowerIterator.next();
				action = new Action(rowerAID, assignBoat);
				sendMessage(ACLMessage.INFORM, rowerAID, action,
						Constant.ASSIGN_BOAT_PROTOCOL);
				System.out.println(getLocalName() + ": assign boat to "
						+ rowerAID.getLocalName());

				// Insert rower into the rower table
				int rowerType = Integer.parseInt(rowerAID.getLocalName()
						.substring(rowerAID.getLocalName().length() - 1),
						rowerAID.getLocalName().length());
				LocalRower rower = null;

				switch (rowerType) {
				case 1:
					rower = new LocalRower(rowerAID.getLocalName(),
							rower1Strength, rower1Resistance, rower1Agility);
					rower.setTimeRow(rowSeconds - rower1Agility);
					break;
				case 2:
					rower = new LocalRower(rowerAID.getLocalName(),
							rower2Strength, rower2Resistance, rower2Agility);
					rower.setTimeRow(rowSeconds - rower2Agility);
					break;
				case 3:
					rower = new LocalRower(rowerAID.getLocalName(),
							rower3Strength, rower3Resistance, rower3Agility);
					rower.setTimeRow(rowSeconds - rower3Agility);
					break;
				case 4:
					rower = new LocalRower(rowerAID.getLocalName(),
							rower4Strength, rower4Resistance, rower4Agility);
					rower.setTimeRow(rowSeconds - rower4Agility);
					break;
				default:
					break;
				}

				rowerHashMap.put(rowerAID, rower);
			}
		} catch (StaleProxyException e) {
			e.printStackTrace();
		} catch (CodecException e) {
			e.printStackTrace();
		} catch (OntologyException e) {
			e.printStackTrace();
		}
	}

	/**
	 * Handles the details of the boat creation.
	 * 
	 * @param boatAID
	 *            the AID of the assigned boat
	 * @return boat ontology
	 */
	private upm.pb4agent.ontology.Boat initiateBoat(AID boatAID) {
		upm.pb4agent.ontology.Boat boat = new upm.pb4agent.ontology.Boat();
		Seat seat = null;

		boat.setID(boatAID);

		seat = new Seat();
		seat.setSeatID("1");
		boat.addSeats(seat);

		seat = new Seat();
		seat.setSeatID("2");
		boat.addSeats(seat);

		seat = new Seat();
		seat.setSeatID("3");
		boat.addSeats(seat);

		seat = new Seat();
		seat.setSeatID("4");
		boat.addSeats(seat);

		seat = new Seat();
		seat.setSeatID("5");
		boat.addSeats(seat);

		seat = new Seat();
		seat.setSeatID("6");
		boat.addSeats(seat);

		return boat;
	}

	/**
	 * Subscribes for start race signal from Referee.
	 * 
	 * @throws FIPAException
	 * @throws CodecException
	 * @throws OntologyException
	 */
	private void subscribeRefereeService() throws FIPAException,
			CodecException, OntologyException, InterruptedException {
		AID refereeAID = AgentUtil.searchAgentInDF(this,
				Constant.REFEREE_DF_TYPE);

		Action action = new Action(refereeAID, new SubscribeReferee());

		sendMessage(ACLMessage.SUBSCRIBE, refereeAID, action,
				Constant.SUBSCRIBE_REFEREE_PROTOCOL);
		System.out.println(getLocalName()
				+ ": Send message to subscribe to referee");

		ACLMessage subscribeReplyMessage = blockingReceive(MessageTemplate.and(
				standardTemplate, MessageTemplate
						.MatchProtocol(Constant.SUBSCRIBE_REFEREE_PROTOCOL)));

		switch (subscribeReplyMessage.getPerformative()) {
		case ACLMessage.AGREE:
			System.out.println(getLocalName()
					+ ": Subscribed for start race alert");
			break;

		case ACLMessage.REFUSE:
			System.out.println(getLocalName() + ": Subscription was refused");
			break;

		default:
			break;
		}
	}

	/**
	 * Receives start race signal from Referee.
	 * 
	 * @author Dave Rojas
	 * 
	 */
	class ReceiveRaceStartedSignal extends SimpleBehaviour {

		private static final long serialVersionUID = 7899280705737118500L;

		public ReceiveRaceStartedSignal(Agent agent) {
			super(agent);
		}

		@Override
		public void action() {
			ACLMessage raceStartedMessage = receive(MessageTemplate.and(
					MessageTemplate.and(standardTemplate, MessageTemplate
							.MatchPerformative(ACLMessage.INFORM)),
					MessageTemplate
							.MatchProtocol(Constant.RACE_STARTED_PROTOCOL)));

			if (raceStartedMessage != null) {
				ContentElement contentElement;
				try {

					contentElement = getContentManager().extractContent(
							raceStartedMessage);

					if (contentElement instanceof Action) {

						Action action = (Action) contentElement;
						Concept concept = action.getAction();

						if (concept instanceof RaceStarted) {
							isRaceStarted = true;
							System.out
									.println(getLocalName()
											+ ": Alert message is received. Race is started");
						}
					}
				} catch (UngroundedException e) {
					e.printStackTrace();
				} catch (CodecException e) {
					e.printStackTrace();
				} catch (OntologyException e) {
					e.printStackTrace();
				}
			} else {

				block();
			}
		}

		@Override
		public boolean done() {
			return isRaceStarted == true;
		}
	}

	/**
	 * Receives race finished signal from Referee
	 * 
	 * @author Dave Rojas
	 * 
	 */
	class ReceiveRaceFinishedSignal extends SimpleBehaviour {

		private static final long serialVersionUID = -6969207668891375109L;

		public ReceiveRaceFinishedSignal(Agent agent) {
			super(agent);
		}

		@Override
		public void action() {
			ACLMessage raceFinishedMessage = receive(MessageTemplate.and(
					MessageTemplate.and(standardTemplate, MessageTemplate
							.MatchPerformative(ACLMessage.INFORM)),
					MessageTemplate
							.MatchProtocol(Constant.RACE_FINISHED_PROTOCOL)));

			if (raceFinishedMessage != null) {
				ContentElement contentElement;
				try {

					contentElement = getContentManager().extractContent(
							raceFinishedMessage);

					if (contentElement instanceof Action) {

						Action action = (Action) contentElement;
						Concept concept = action.getAction();

						if (concept instanceof RaceFinished) {
							RaceFinished raceFinished = (RaceFinished) concept;

							if (raceFinished.getBoatAID().equals(boatAID)) {
								isRaceFinished = true;
								System.out
										.println(getLocalName()
												+ ": Alert message is received. Race is finished");
							}
						}
					}
				} catch (UngroundedException e) {
					e.printStackTrace();
				} catch (CodecException e) {
					e.printStackTrace();
				} catch (OntologyException e) {
					e.printStackTrace();
				}
			} else {

				block();
			}
		}

		@Override
		public boolean done() {
			return isRaceFinished == true;
		}
	}

	/**
	 * Receives the row request from rower. This class transforms the request
	 * into a new boat coordinate.
	 * 
	 * @author Bob Nicolaus
	 * 
	 */
	class ReceiveRowRequest extends SimpleBehaviour {

		private static final long serialVersionUID = -8332224797821610780L;

		public ReceiveRowRequest(Agent agent) {
			super(agent);
		}

		@Override
		public void action() {

			boolean isMessageValid = true;

			if (isRaceStarted == true && isRaceFinished == false) {
				ACLMessage rowMessage = receive(MessageTemplate.and(
						MessageTemplate.and(standardTemplate, MessageTemplate
								.MatchPerformative(ACLMessage.REQUEST)),
						MessageTemplate.MatchProtocol(Constant.ROW_PROTOCOL)));

				if (rowMessage != null) {
					ContentElement contentElement;
					try {

						contentElement = getContentManager().extractContent(
								rowMessage);

						if (contentElement instanceof Action) {

							Action action = (Action) contentElement;
							Concept concept = action.getAction();

							if (concept instanceof Row) {
								Row row = (Row) concept;

								long currentTime = new Date().getTime();
								long lastRowTimeStamp = 0;

								LocalRower rower = rowerHashMap.get(rowMessage
										.getSender());
								if (rower.getOarHashMap().containsKey(
										row.getOarID()) == true) {

									lastRowTimeStamp = rower.getOarHashMap()
											.get(row.getOarID());
								}

								// Refuse
								if (lastRowTimeStamp != 0
										&& currentTime - lastRowTimeStamp < rower
												.getTimeRow()) {

									System.out
											.println(myAgent.getLocalName()
													+ ": Row of "
													+ rowMessage.getSender()
															.getLocalName()
													+ " for oar "
													+ row.getOarID()
													+ " is denied ! CurrentTime - lastRowTimeStamp = "
													+ (currentTime - lastRowTimeStamp)
													+ " is < than timeRow = "
													+ rower.getTimeRow());

									DenyRow denyRow = new DenyRow();
									denyRow.setOarID(row.getOarID());

									action = new Action(rowMessage.getSender(),
											denyRow);
									replyMessage(rowMessage, ACLMessage.REFUSE,
											action, Constant.DENY_ROW_PROTOCOL);
								} else {

									System.out.println(myAgent.getLocalName()
											+ ": "
											+ row.getBoat().getID()
													.getLocalName() + " oar "
											+ row.getOarID() + " strength "
											+ row.getStrength());

									// Calculate new coordinate
									BoatCoordinate coordinate = boatHashMap
											.get(row.getBoat().getID());
									coordinate.setOrdinate(coordinate
											.getOrdinate()
											+ AgentUtil.calculateMovementY(
													row.getStrength(),
													rower.getStrength(),
													rower.getTiredness(),
													row.getOarID(),
													row.getDirection()));
									coordinate.setAbscissa(coordinate
											.getAbscissa()
											+ AgentUtil.calculateMovementX(
													row.getStrength(),
													rower.getStrength(),
													rower.getTiredness(),
													row.getOarID()));

									// Update rower (rowerHashMap)
									rower.getOarHashMap().put(row.getOarID(),
											currentTime);

									InformCoordinate informCoordinate = new InformCoordinate();
									informCoordinate.setOrdinate(coordinate
											.getOrdinate());
									informCoordinate.setAbscissa(coordinate
											.getAbscissa());

									// Inform the rower
									RealRow realRow = new RealRow();
									realRow.setY(coordinate.getOrdinate());
									realRow.setX(coordinate.getAbscissa());
									realRow.setOarID(row.getOarID());

									action = new Action(rowMessage.getSender(),
											realRow);
									replyMessage(rowMessage, ACLMessage.INFORM,
											action, Constant.REAL_ROW_PROTOCOL);

									// Inform the boat
									action = new Action(row.getBoat().getID(),
											informCoordinate);
									sendInternalMessage(ACLMessage.INFORM, row
											.getBoat().getID(), action,
											Constant.INFORM_COORDINATE_PROTOCOL);
								}

								// Either successful or not, update the
								// tiredness and time to row
								rower.setTiredness(AgentUtil
										.calculateTotalTiredness(
												rower.getTiredness(),
												row.getStrength(),
												rower.getResistance()));
								rower.setTimeRow(rower.getTimeRow()
										+ rower.getTiredness());
							} else {
								isMessageValid = false;
							}
						} else {
							isMessageValid = false;
						}
					} catch (UngroundedException e) {
						e.printStackTrace();
					} catch (CodecException e) {
						e.printStackTrace();
					} catch (OntologyException e) {
						e.printStackTrace();
					}

					if (isMessageValid == false) {
						replyMessage(rowMessage, ACLMessage.NOT_UNDERSTOOD,
								Constant.ROW_PROTOCOL);
					}
				} else {

					block();
				}
			}
		}

		@Override
		public boolean done() {
			return isRaceFinished == true;
		}
	}

	/**
	 * Helper class for boat coordinate.
	 * 
	 * @author Bob Nicolaus
	 * 
	 */
	class BoatCoordinate {
		private int ordinate;
		private int abscissa;

		public BoatCoordinate() {
			ordinate = 0;
			abscissa = 0;
		}

		public int getOrdinate() {
			return ordinate;
		}

		public void setOrdinate(int ordinate) {
			this.ordinate = ordinate;
		}

		public int getAbscissa() {
			return abscissa;
		}

		public void setAbscissa(int abscissa) {
			this.abscissa = abscissa;
		}

	}
}
